package no.ntnu.fp.client;

import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import no.ntnu.fp.connection.Initializer;
import no.ntnu.fp.gui.EventDispatcher;
import no.ntnu.fp.gui.calendar.CalendarPanel;
import no.ntnu.fp.gui.dialog.ParticipantDialog;
import no.ntnu.fp.gui.events.AcceptNoticeEvent;
import no.ntnu.fp.gui.events.AddOrUpdateAppointmentEvent;
import no.ntnu.fp.gui.events.AddMeetingEvent;
import no.ntnu.fp.gui.events.CancelMeetingEvent;
import no.ntnu.fp.gui.events.ChangeMeetingEvent;
import no.ntnu.fp.gui.events.DeleteAppointmentEvent;
import no.ntnu.fp.gui.events.EmployeesRequestedEvent;
import no.ntnu.fp.gui.events.GuiEvent;
import no.ntnu.fp.gui.events.LoginRequestedEvent;
import no.ntnu.fp.gui.events.LogoutRequestedEvent;
import no.ntnu.fp.gui.events.MeetingRoomsRequestedEvent;
import no.ntnu.fp.gui.events.NoticeReadEvent;
import no.ntnu.fp.gui.events.RejectNoticeEvent;
import no.ntnu.fp.gui.events.RemoveEmployeeCalendarEvent;
import no.ntnu.fp.gui.events.RequestCalendarForEmployeeEvent;
import no.ntnu.fp.gui.listeners.AcceptNoticeListener;
import no.ntnu.fp.gui.listeners.AddAppointmentListener;
import no.ntnu.fp.gui.listeners.AddMeetingListener;
import no.ntnu.fp.gui.listeners.CancelMeetingListener;
import no.ntnu.fp.gui.listeners.ChangeMeetingListener;
import no.ntnu.fp.gui.listeners.DeleteAppointmentListener;
import no.ntnu.fp.gui.listeners.EmployeesRequestedListener;
import no.ntnu.fp.gui.listeners.GuiListener;
import no.ntnu.fp.gui.listeners.LoginListener;
import no.ntnu.fp.gui.listeners.LogoutListener;
import no.ntnu.fp.gui.listeners.MeetingRoomsRequestedListener;
import no.ntnu.fp.gui.listeners.NoticeReadListener;
import no.ntnu.fp.gui.listeners.RejectNoticeListener;
import no.ntnu.fp.gui.listeners.RemoveEmployeeCalendarListener;
import no.ntnu.fp.gui.listeners.RequestCalendarForEmployeeListener;
import no.ntnu.fp.gui.login.SettingsDialog;
import no.ntnu.fp.gui.mainFrame.MainFrame;
import no.ntnu.fp.gui.mainFrame.Toolbar;
import no.ntnu.fp.handler.Handler;
import no.ntnu.fp.handlers.AlreadyLoggedInHandler;
import no.ntnu.fp.handlers.AppointmentAddedOrUpdatedHandler;
import no.ntnu.fp.handlers.AppointmentDeletedHandler;
import no.ntnu.fp.handlers.AppointmentsForEmployeeHandler;
import no.ntnu.fp.handlers.EmployeesFetchedHandler;
import no.ntnu.fp.handlers.LoginFailedHandler;
import no.ntnu.fp.handlers.LoginSuccessfulHandler;
import no.ntnu.fp.handlers.LogoutSuccessfulHandler;
import no.ntnu.fp.handlers.MeetingAddedHandler;
import no.ntnu.fp.handlers.MeetingDeletedHandler;
import no.ntnu.fp.handlers.MeetingRoomsFetchedHandler;
import no.ntnu.fp.handlers.MeetingUpdatedHandler;
import no.ntnu.fp.handlers.MeetingsForEmployeeHandler;
import no.ntnu.fp.handlers.NoticeAddedOrUpdatedHandler;
import no.ntnu.fp.messages.MessageCode;
import no.ntnu.fp.model.CalendarModel;
import no.ntnu.fp.model.DialogModel;
import no.ntnu.fp.model.LoggedInModel;
import no.ntnu.fp.model.NoticeModel;
import no.ntnu.fp.model.events.ModelEvent;
import no.ntnu.fp.model.events.NoticesChangedEvent;
import no.ntnu.fp.model.listeners.ModelListener;
import no.ntnu.fp.net.co.Connection;
import no.ntnu.fp.net.co.ConnectionImpl2;
import no.ntnu.fp.services.AppointmentService;
import no.ntnu.fp.services.EmployeeService;
import no.ntnu.fp.services.MeetingRoomService;
import no.ntnu.fp.services.MeetingService;
import no.ntnu.fp.services.NoticeService;
import no.ntnu.fp.services.impl.AppointmentServiceImpl;
import no.ntnu.fp.services.impl.EmployeeServiceImpl;
import no.ntnu.fp.services.impl.MeetingRoomServiceImpl;
import no.ntnu.fp.services.impl.MeetingServiceImpl;
import no.ntnu.fp.services.impl.NoticeServiceImpl;

public class InitializerImpl extends Initializer {
	
	private final Connection connection = new ConnectionImpl2(Client.getPortNumber());
	
	private final AppointmentService appointmentService = new AppointmentServiceImpl(connection);
	private final EmployeeService employeeService = new EmployeeServiceImpl(connection);
	private final MeetingRoomService meetingRoomService = new MeetingRoomServiceImpl(connection);
	private final MeetingService meetingService = new MeetingServiceImpl(connection);
	private final NoticeService noticeService = new NoticeServiceImpl(connection);
	
	private final CalendarModel calendarModel = new CalendarModel();
	private final DialogModel dialogModel;
	private final LoggedInModel loggedInModel = new LoggedInModel();
	private final NoticeModel noticeModel = new NoticeModel();
	
	private final MainFrame mainFrame;
	
	@SuppressWarnings("serial")
	private final Calendar calendar = new GregorianCalendar() {

		@Override
		public int getActualMinimum(int field) {
			if (field != WEEK_OF_MONTH) {
				return super.getActualMinimum(field);					
			} 
			
			Calendar c = (Calendar)clone();
			c.set(DATE, 1);
			int minimum = c.get(WEEK_OF_MONTH);
			return minimum;
		}
		
	};

	private static final UncaughtExceptionHandler uncaughtExceptionHandler = new UncaughtExceptionHandler() {
		@Override
		public void uncaughtException(Thread t, final Throwable e) {
			if (SwingUtilities.isEventDispatchThread()) {
				//TODO: Bytt ut disse med en egen feilmeldingsboks
				e.printStackTrace();
				JOptionPane.showMessageDialog(null, "Alt gikk til helvete!\n\n" + e.toString());
			} else {
				SwingUtilities.invokeLater(new Runnable() {
					@Override
					public void run() {
						//TODO: Bytt ut disse med en egen feilmeldingsboks
						e.printStackTrace();
						JOptionPane.showMessageDialog(null, "Alt gikk til helvete!\n\n" + e.toString());
					}
				});
			}
		}
	};
	
	public static class AwtHandler {
		public void handle(Throwable t) {
			uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), t);
		}
	}
	
	public InitializerImpl() throws InterruptedException, ExecutionException {
		calendar.setFirstDayOfWeek(Calendar.MONDAY);
		DialogModel.createInstance();
		this.dialogModel = DialogModel.getInstance();
		
		FutureTask<MainFrame> task = new FutureTask<MainFrame>(new Callable<MainFrame>() {
			@Override
			public MainFrame call() throws Exception {
				CalendarPanel.createInstance(calendar);
				ParticipantDialog.createInstance();
				Toolbar.createInstance();
				return new MainFrame(calendar);
			}
		});
		
		SwingUtilities.invokeLater(task);
		mainFrame = task.get();
	}
	
	@Override
	public void initialize() {
		super.initialize();
		registerUncaughtExceptionHandlers();
		
		initializeGuiListeners();
		initializeModelListeners();
		
		showGui();
	}
	
	@Override
	protected Map<MessageCode, Handler> getMessageHandlers() {
		Map<MessageCode, Handler> map = new HashMap<MessageCode, Handler>();
		
		map.put(MessageCode.LOGIN_SUCCESSFUL_MESSAGE, 
				new LoginSuccessfulHandler(calendarModel, noticeModel, 
																loggedInModel));
		map.put(MessageCode.LOGIN_FAILED_MESSAGE, 
				new LoginFailedHandler(connection, loggedInModel));
		
		map.put(MessageCode.LOGOUT_SUCCESSFUL_MESSAGE, 
				new LogoutSuccessfulHandler(loggedInModel, calendarModel,
											dialogModel, noticeModel, connection));
		
		map.put(MessageCode.EMPLOYEES_FETCHED_MESSAGE,
				new EmployeesFetchedHandler(dialogModel, loggedInModel));
		
		map.put(MessageCode.MEETING_ROOMS_FETCHED_MESSAGE, 
				new MeetingRoomsFetchedHandler(dialogModel));
		
		map.put(MessageCode.NOTICE_ADDED_OR_UPDATED_MESSAGE,
				new NoticeAddedOrUpdatedHandler(noticeModel));

		map.put(MessageCode.APPOINTMENT_ADDED_OR_UPDATED_MESSAGE,
				new AppointmentAddedOrUpdatedHandler(calendarModel));

		
		map.put(MessageCode.APPOINTMENT_DELETED_MESSAGE,
				new AppointmentDeletedHandler(calendarModel));
		
		map.put(MessageCode.MEETING_ADDED_MESSAGE,
				new MeetingAddedHandler(calendarModel));
		
		map.put(MessageCode.MEETING_UPDATED_MESSAGE,
				new MeetingUpdatedHandler(calendarModel));
		
		map.put(MessageCode.MEETING_DELETED_MESSAGE,
				new MeetingDeletedHandler(calendarModel));
		
		map.put(MessageCode.APPOINTMENTS_FOR_EMPLOYEE_MESSAGE,
				new AppointmentsForEmployeeHandler(calendarModel));
		
		map.put(MessageCode.MEETINGS_FOR_EMPLOYEE_MESSAGE,
				new MeetingsForEmployeeHandler(calendarModel));
		
		map.put(MessageCode.ALREADY_LOGGED_IN_MESSAGE,
				new AlreadyLoggedInHandler(loggedInModel));
		
		
		return map;
	}
	
	private void registerUncaughtExceptionHandlers() {
		Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
		System.setProperty("sun.awt.exception.handler", AwtHandler.class.getName());
	}

	private void initializeGuiListeners() {
		Map<Class<? extends GuiEvent>, GuiListener<?>> map = 
			new HashMap<Class<? extends GuiEvent>, GuiListener<?>>();
		
		map.put(LoginRequestedEvent.class, new LoginListener(connection, employeeService));
		map.put(LogoutRequestedEvent.class, new LogoutListener(connection));
		map.put(AddOrUpdateAppointmentEvent.class, new AddAppointmentListener(appointmentService));
		map.put(EmployeesRequestedEvent.class, new EmployeesRequestedListener(employeeService));
		map.put(MeetingRoomsRequestedEvent.class, new MeetingRoomsRequestedListener(meetingRoomService));
		map.put(DeleteAppointmentEvent.class, new DeleteAppointmentListener(appointmentService));
		map.put(AddMeetingEvent.class, new AddMeetingListener(meetingService));
		map.put(ChangeMeetingEvent.class, new ChangeMeetingListener(meetingService));
		map.put(CancelMeetingEvent.class, new CancelMeetingListener(meetingService));
		map.put(RequestCalendarForEmployeeEvent.class, new RequestCalendarForEmployeeListener(appointmentService, meetingService));
		map.put(RemoveEmployeeCalendarEvent.class, new RemoveEmployeeCalendarListener(calendarModel));
		map.put(AcceptNoticeEvent.class, new AcceptNoticeListener(noticeService));
		map.put(RejectNoticeEvent.class, new RejectNoticeListener(noticeService));
		map.put(NoticeReadEvent.class, new NoticeReadListener(noticeService));
		
		EventDispatcher.registerGuiListeners(map);
	}
	
	private void initializeModelListeners() {
		Map<Class<? extends ModelEvent>, ModelListener<?>> eventListeners 
		= mainFrame.getEventListeners();
		EventDispatcher.registerModelListeners(eventListeners);
	}

	public void showGui() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				mainFrame.setVisible(true);
			}
		});
	}
}

